package com.elitesland.security.config;

import com.elitesland.core.util.RedisUtils;
import com.elitesland.security.JsonLoginProcessor;
import com.elitesland.security.LoginPostProcessor;
import com.elitesland.security.TokenProvider;
import com.elitesland.security.config.bean.JwtProperties;
import com.elitesland.security.filter.JwtTokenFilter;
import com.elitesland.security.filter.PreLoginFilter;
import com.elitesland.security.handle.JsonAuthenticationEntryPoint;
import com.elitesland.security.handle.SimpleAccessDeniedException;
import com.elitesland.security.service.OnlineUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.annotation.Resource;
import java.util.Collection;

/**
 * <pre>
 * [功能说明]
 * </pre>
 *
 * @author Mir
 * @date 2020/6/15
 */
@Configuration
@ConditionalOnClass(WebSecurityConfigurerAdapter.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@RequiredArgsConstructor
public class SecurityConfig {

    private static final String LOGIN_PROCESSING_URL = "/process";

    private final JwtProperties jwtProperties;

    private final OnlineUserService onlineUserService;

    private final TokenProvider tokenProvider;

    @Bean
    public JsonLoginProcessor jsonLoginProcessor() {
        return new JsonLoginProcessor();
    }

    @Bean
    public PreLoginFilter preLoginFilter(Collection<LoginPostProcessor> loginPostProcessors, RedisUtils redisUtils) {
        return new PreLoginFilter(LOGIN_PROCESSING_URL, loginPostProcessors, redisUtils);
    }

    @Bean
    public JwtTokenFilter jwtTokenFilter() {
        return new JwtTokenFilter(jwtProperties, onlineUserService, tokenProvider);
    }

    @Configuration
    @Order(SecurityProperties.BASIC_AUTH_ORDER)
    static class SecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

        @Resource
        private AuthenticationSuccessHandler authenticationSuccessHandler;

        @Resource
        private AuthenticationFailureHandler authenticationFailureHandler;

        @Resource
        private PreLoginFilter preLoginFilter;

        @Resource
        private JwtTokenFilter jwtTokenFilter;

        @Resource
        private FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource;

        @Resource
        private AccessDecisionManager accessDecisionManager;

        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            super.configure(auth);
        }

        @Override
        public void configure(WebSecurity web) throws Exception {
            super.configure(web);
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable()
                    .cors()
                    .and()
                    .exceptionHandling()
                    .authenticationEntryPoint(new JsonAuthenticationEntryPoint())
                    .accessDeniedHandler(new SimpleAccessDeniedException())
                    .and()
                    .authorizeRequests()
                    .antMatchers("/sys/**").permitAll()
                    // 静态资源等等
                    .antMatchers(
                            HttpMethod.GET,
                            "/*.html",
                            "/**/*.html",
                            "/**/*.css",
                            "/**/*.js",
                            "/webSocket/**"
                    ).permitAll()
                    // swagger 文档
                    .antMatchers("/swagger-ui.html").permitAll()
                    .antMatchers("/swagger-resources/**").permitAll()
                    .antMatchers("/webjars/**").permitAll()
                    .antMatchers("/*/api-docs").permitAll()
                    // 文件
                    .antMatchers("/avatar/**").permitAll()
                    .antMatchers("/file/**").permitAll()
                    // 阿里巴巴 druid
                    .antMatchers("/druid/**").permitAll()
                    // 放行OPTIONS请求
                    .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                    .anyRequest().authenticated()
                    .withObjectPostProcessor(filterSecurityInterceptorObjectPostProcessor())
                    .and()
                    .addFilterBefore(preLoginFilter, UsernamePasswordAuthenticationFilter.class)
                    .addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class)
                    .formLogin()
                    .loginProcessingUrl(LOGIN_PROCESSING_URL)
                    .successHandler(authenticationSuccessHandler).failureHandler(authenticationFailureHandler);
        }

        private ObjectPostProcessor<FilterSecurityInterceptor> filterSecurityInterceptorObjectPostProcessor() {
            return new ObjectPostProcessor<>() {
                @Override
                public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                    o.setAccessDecisionManager(accessDecisionManager);
                    o.setSecurityMetadataSource(filterInvocationSecurityMetadataSource);
                    return o;
                }
            };
        }
    }
}
